import 'dart:async';

import '../bloc.dart';
import 'package:meta/meta.dart';
import 'package:rxdart/rxdart.dart';

/// 新版本引入Stream Sink来处理流，dispatch方法替换为add
abstract class Bloc<Event, State> extends Stream<State> implements Sink<Event> {
  final PublishSubject<Event> _eventSubject = PublishSubject<Event>();

  BehaviorSubject<State> _stateSubject;

  State get state => _stateSubject.value;

  State get initialState;

  /// 返回当前Stream是否是广播类型
  @override
  bool get isBroadcast => _stateSubject.isBroadcast;

  Bloc() {
    _stateSubject = BehaviorSubject<State>.seeded(initialState);
    _bindStateSubject();
  }

  /// 由于Bloc继承自Stream所以可以监听流的State来处理逻辑
  @override
  StreamSubscription<State> listen(
    void Function(State) onData, {
    Function onError,
    void Function() onDone,
    bool cancelOnError,
  }) {
    return _stateSubject.listen(
      onData,
      onError: onError,
      onDone: onDone,
      cancelOnError: cancelOnError,
    );
  }

  void onEvent(Event event) => null;

  /// 该方法适合添加日志和分析，delegate.onTransition为全局调用，onTransition为当前bloc调用
  void onTransition(Transition<Event, State> transition) => null;

  void onError(Object error, StackTrace stacktrace) => null;

  @override
  void add(Event event) {
    try {
      BlocSupervisor.delegate.onEvent(this, event);
      onEvent(event);
      _eventSubject.sink.add(event);
    } on dynamic catch (error) {
      _handleError(error);
    }
  }

  /// 相当于dispose回调，如果event已经add之后在close，已经add的event会等到执行完成
  @override
  @mustCallSuper
  Future<void> close() async {
    await _eventSubject.close();
    await _stateSubject.close();
  }

  /// asyncExpand会确保event顺序执行，在某些情景下需要重写该方法，可以同时执行处理
  Stream<State> transformEvents(
    Stream<Event> events,
    Stream<State> Function(Event) next,
  ) {
    return events.asyncExpand(next);
  }

  Stream<State> mapEventToState(Event event);

  /// 同transformEvents，在某些场景下需要对state做后处理
  /// 例子：
  /// Stream<State> transformStates(Stream<State> states) {
  ///   return states.debounceTime(Duration(seconds: 1));
  /// }
  Stream<State> transformStates(Stream<State> states) => states;

  void _bindStateSubject() {
    Event currentEvent;

    transformStates(transformEvents(_eventSubject, (event) {
      currentEvent = event;
      return mapEventToState(currentEvent).handleError(_handleError);
    })).forEach(
      (nextState) {
        // todo state == nextState
        if (_stateSubject.isClosed) return;
        final transition = Transition(
          currentState: state,
          event: currentEvent,
          nextState: nextState,
        );
        try {
          BlocSupervisor.delegate.onTransition(this, transition);
          onTransition(transition);
          _stateSubject.add(nextState);
        } on dynamic catch (error) {
          _handleError(error);
        }
      },
    );
  }

  void _handleError(Object error, [StackTrace stacktrace]) {
    BlocSupervisor.delegate.onError(this, error, stacktrace);
    onError(error, stacktrace);
  }
}
